package com.AdvancedDemos.LearnThread多线程;

import java.util.ArrayList;
import java.util.List;

public class 线程通信_生产者消费者模型 {
    public static void main(String[] args) {
        /**
         *需求:3个生产者线程，负责生产包子，每个线程每次只能生产1个包子放在桌子上
         * 2个消费者线程负责吃包子，每人每次只能从桌子上拿1个包子吃。
         */
        //创建桌子
        Desk desk = new Desk();
        //创建3个生产者
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    desk.put();//放一个包子
                }
            }
        }, "厨子1").start();

        new Thread(() -> {
            while (true) {
                desk.put();//放一个包子
            }
        }, "厨子2").start();

        new Thread(() -> {
            while (true) {
                desk.put();//放一个包子
            }
        }, "厨子3").start();

        //创建2个消费者
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    desk.get();//拿一个包子
                }
            }
        }, "吃货1").start();

        new Thread(() -> {
            while (true) {
                desk.get();//拿一个包子
            }
        }, "吃货2").start();
    }
}

class Desk {
    private List<String> list = new ArrayList<>();//放包子的容器

    //放一个包子 (3个厨子竞争)
    public synchronized void put() {
        String name = Thread.currentThread().getName();
        //判断是否有包子
        if (list.size() == 0) {
            list.add(name + "做的人肉包子");
            System.out.println(name + "做了一个人肉包子!");
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }

            //唤醒别人,等待自己,让出锁
            /**
             * Object类的线程通信常用方法,即等待和唤醒方法:
             *
             * void wait() --- 让当前线程等待并释放所占锁，直到另一个线程调用notify()方法或notifyAl1()方法
             * void notify() --- 唤醒正在等待的单个线程
             * void notifyAll() --- 唤醒正在等待的所有线程
             *
             * 注意:上述方法应该使用当前同步锁对象进行调用。
             */

            this.notifyAll();//应该先唤醒别人
            try {
                this.wait();//自己再晕过去...
            } catch (Exception e) {
                e.printStackTrace();
            }

        } else {
            //已经有包子了,停产
            //唤醒别人,等待自己
            this.notifyAll();
            try {
                this.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //拿一个包子 (2个吃货竞争)
    public synchronized void get() {
        String name = Thread.currentThread().getName();
        if (list.size() == 1) {
            //有包子,吃包子
            System.out.println(name + "吃了:" + list.get(0));
            list.clear();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.notifyAll();
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /*
    put() get() 两个方法都使用了同一个锁对象this,即当前Desk对象;锁可以跨方法,因此,同时将只会有一个线程能调用put或get方法,放或取一个包子,不可能同时放,或者同时取,或者同时存取
     */

}